﻿package zf.data.geom
{
	import flash.geom.Point;

	/**
	 * Linear 对象表示二维坐标系统中的某条线段，其包括起始点和终止点。 
	 * 主要的直线方程有 y = k x + b , A x + B y + C =0;
	 * 本类使用方程为： y = k x + b 
	 * @author Mike.zhang
	 * 
	 */
	public class Linear implements ILine
	{
		private var _sp:Point;
		private var _ep:Point;
		private var _k:Number;
		private var _b:Number;
		private var _radian:Number;
		/**
		 * 创建线段
		 * @param startPoint 线段的起点
		 * @param endPoint 线段的终点
		 * @throws ArgumentError 如果起点和终点相同会引发 ArgumentError 异常
		 */
		public function Linear (startPoint:Point,endPoint:Point)
		{
			if (startPoint == null || endPoint == null || startPoint.equals(endPoint))
			{
				throw new ArgumentError("线段的起点和终点不能相同,切不能为空");
			}
			_sp=startPoint;
			_ep=endPoint;
			account ();
		}

		/**
		 * 线段起始点
		 * @return 线段起点 
		 * @throws ArgumentError 如果起点和终点相同会引发 ArgumentError 异常
		 */
		public function get startPoint ():Point
		{
			return _sp;
		}

		public function set startPoint (v:Point):void
		{
			if (v.equals(_ep))
			{
				throw new ArgumentError("线段的起点和终点不能相同");
			}
			if (_sp.equals(v) == false)
			{
				_sp=v;
				account ();
			}
		}

		/**
		 * 线段终止点
		 * @return 线段终点
		 * @throws ArgumentError 如果起点和终点相同会引发 ArgumentError 异常
		 */
		public function get endPoint ():Point
		{
			return _ep;
		}

		public function set endPoint (v:Point):void
		{
			if (v.equals(_sp))
			{
				throw new ArgumentError("线段的起点和终点不能相同");
			}
			if (_ep.equals(v) == false)
			{
				_ep=v;
				account ();
			}
		}
		/**
		 * 计算k,b,radian值
		 * @private 
		 * 
		 */
		private function account ():void
		{
			_k=_ep.y - _sp.y / _ep.x - _sp.x;
			_b=_ep.x * _sp.y - _sp.x * _ep.y / _ep.x - _sp.x;
			_radian=Math.atan2(_ep.y - _sp.y,_ep.x - _sp.x);
		}

		/**
		 * 直线方程y=kx+b中的k值 
		 * @return 直线k值
		 * 
		 */
		public function get k ():Number
		{
			return _k;
		}

		/**
		 * 直线方程y=kx+b中的b值 
		 * @return 直线b值
		 * 
		 */
		public function get b ():Number
		{
			return _b;
		}

		/**
		 * 以弧度为单位返回线段与x轴正方向的角，该角度从圆的 x 轴（0 点在其上，0 表示圆心）沿逆时针方向测量。 返回值介于正 pi 和负 pi 之间。
		 * @return 弧度值
		 * 
		 */
		public function get radian ():Number
		{
			return _radian;
		}
		/**
		 * 检测点是否与线段有交点，如果有，该点即为交点。 
		 * @param p 待检测的点。
		 * @param absolute 当该参数为true时只检测线段的起始点到终止点部分与该点相交情况，默认为flash，检测直线
		 * @return 如果有返回true。
		 * 
		 */
		public function hitPoint (p:Point,absolute:Boolean=false):Boolean
		{
			if (p.y == _k * p.x + _b)
			{
				if (absolute)
				{
					var maxx:Number;
					var minx:Number;
					var maxy:Number;
					var miny:Number;
					if (_sp.x > _ep.x)
					{
						maxx=_sp.x;
						minx=_ep.x;
					}
					else
					{
						maxx=_ep.x;
						minx=_sp.x;
					}
					if (_sp.y > _ep.y)
					{
						maxy=_sp.y;
						miny=_ep.y;
					}
					else
					{
						maxy=_ep.y;
						miny=_sp.y;
					}
					if (maxx >= p.x && p.x >= minx && maxy >= p.y && p.y >= miny)
					{
						return true;
					}
				}
				else
				{
					return true;
				}
			}

			return false;
		}
		/**
		 * 是否和线段相等，即起点和终点完全相等 
		 * @param l 待检测的线段
		 * @return 相等返回true
		 * 
		 */
		public function equals (l:Linear):Boolean
		{
			return _sp.equals(l.startPoint) && _ep.equals(l.endPoint);
		}
		/**
		 * 线段重合检测 
		 * @param l 待检测的线段
		 * @param absolute 当该参数为true时，只检测两条线段的起始点与终止点部分，默认为flash，检测两条直线
		 * @return 部分重合即为true
		 * 
		 */
		public function superposition (l:Linear,absolute:Boolean=false):Boolean
		{
			if (k == l.k && b == l.b)
			{
				if (absolute)
				{
					var tminx:Number;
					var tmaxx:Number;
					var pminx:Number;
					var pmaxx:Number;
					if (_sp.x > _ep.x)
					{
						tminx=_ep.x;
						tmaxx=_sp.x;
					}
					else
					{
						tminx=_sp.x;
						tmaxx=_ep.x;
					}
					if (l.startPoint.x > l.endPoint.x)
					{
						pminx=l.endPoint.x;
						pmaxx=l.startPoint.x;
					}
					else
					{
						pminx=l.startPoint.x;
						pmaxx=l.endPoint.x;
					}
					var bool:Boolean=tminx >= pminx && tmaxx <= pmaxx || pminx >= tminx && pmaxx <= tmaxx;
					bool=bool || pminx <= tminx && tminx <= pmaxx || pminx <= tmaxx && tmaxx <= pmaxx;

					bool=bool || tminx <= pminx && pminx <= tmaxx || tminx <= pmaxx && pmaxx <= tmaxx;
					return bool;
				}
				else
				{
					return true;
				}
			}
			return false;
		}
		/**
		 * 获取过点p到线段的垂线
		 * @param p 直线的经过点
		 * @return 直线
		 * 
		 */
		public function getVline (p:Point):Beeline
		{
			var cl:Beeline=new Beeline  ;
			cl.k=-1 / k;
			cl.b=p.x / k + p.y;
			return cl;
		}
		/**
		 * 获取该线段的中垂线 
		 * @return Beeline
		 * 
		 */
		public function getMidVlines ():Beeline
		{
			var mp:Point=Point.interpolate(startPoint,endPoint,0.5);
			var cl:Beeline=new Beeline  ;
			cl.k=-1 / k;
			cl.b=mp.x / k + mp.y;
			return cl;
		}
		/**
		 * 确定线段之间的点。 参数 f 确定新的内插点相对于线段的两个端点所处的位置。 参数 f 的值越接近 1.0，则内插点就越接近 startPoint。 参数 f 的值越接近 0，则内插点就越接近 endPoint。 
		 * @param f 线段的内插级别。 指示新点将位于线段上的什么位置。 如果 f=1，则返回 startPoint；如果 f=0，则返回 endPoint。  
		 * @return 内插点
		 * 
		 */
		public function interpolate (f:Number):Point
		{
			return Point.interpolate(startPoint,endPoint,f);
		}
		/**
		 * 计算线段的过p点垂线到线段或线段延长线上交点的距离，即垂线距离 
		 * @param p 要经过的点p
		 * @return 距离，如果点p在线段及线段的延长线上，返回为0
		 * 
		 */
		public function vlineDistance (p:Point):Number
		{
			if (p.y == k * p.x + b)
			{
				return 0;
			}
			var cl:Beeline=new Beeline  ;
			cl.k=-1 / k;
			cl.b=p.x / k + p.y;

			var xx:Number=b - cl.b / cl.k - k;
			var yy:Number=xx * k + b;
			var px:Point=new Point(xx,yy);

			return Formula.distance(px.x,px.y,p.x,p.y);
		}
		/**
		 * 计算点到线段的最短距离，有可能过该点的与线段垂直的线段长度，或是离起点或终点最近的距离
		 * @param p 要经过的点
		 * @return 距离
		 * 
		 */
		public function pointDistance (p:Point):Number
		{
			if (hitPoint(p,true))
			{
				return 0;
			}
			var cl:Beeline=new Beeline  ;
			cl.k=-1 / k;
			cl.b=p.x / k + p.y;

			var xx:Number=b - cl.b / cl.k - k;
			var yy:Number=xx * k + b;
			var px:Point=new Point(xx,yy);

			if (hitPoint(px,true))
			{
				return Formula.distance(px.x,px.y,p.x,p.y);
			}
			else
			{
				return Math.min(Formula.distance(px.x,px.y,startPoint.x,startPoint.y),Formula.distance(px.x,px.y,endPoint.x,endPoint.y));
			}
			return 0;
		}
		/**
		 * 检测和线段l的相交情况 
		 * @param l 待检测的线段
		 * @param absolute 当该参数为true时，只检测两条线段的起始点与终止点部分的相交情况，默认为flash，检测两条直线
		 * @return 如果有交点将返回交点，否则返回null
		 * 
		 */
		public function hitLinear (l:Linear,absolute:Boolean=false):Point
		{
			if (k == l.k)
			{
				return null;
			}
			var xx:Number=b - l.b / l.k - k;
			var yy:Number=xx * k + b;

			//if(Math.abs(k)==Infinity && Math.abs(b)==Infinity)
			if (startPoint.x == endPoint.x)
			{
				//x=m,垂直于x轴的方程
				xx=startPoint.x;
				yy=l.k * xx + l.b;
			}
			//if(Math.abs(l.k)==Infinity && Math.abs(l.b)==Infinity)
			if (l.startPoint.x == l.endPoint.x)
			{
				//x=m,垂直于x轴的方程
				xx=l.startPoint.x;
				yy=k * xx + b;
			}
			var p:Point=new Point(xx,yy);
			if (absolute)
			{
				var tminx:Number;
				var tmaxx:Number;
				var pminx:Number;
				var pmaxx:Number;
				if (_sp.x > _ep.x)
				{
					tminx=_ep.x;
					tmaxx=_sp.x;
				}
				else
				{
					tminx=_sp.x;
					tmaxx=_ep.x;
				}
				if (l.startPoint.x > l.endPoint.x)
				{
					pminx=l.endPoint.x;
					pmaxx=l.startPoint.x;
				}
				else
				{
					pminx=l.startPoint.x;
					pmaxx=l.endPoint.x;
				}
				if (p.x >= tminx && p.x <= tmaxx && p.x >= pminx && p.x <= pmaxx)
				{
					return p;
				}
				else
				{
					return null;
				}
			}
			return p;
		}
		/**
		 * 获取该线段所在的直线 
		 * @return Beeline
		 * @see Beeline
		 * 
		 */
		public function toBeeline ():Beeline
		{
			return Beeline.PtoBeeline(startPoint,endPoint);
		}
		/**
		 * 获取与线l所成的夹角，返回值在 0 和 pi/2 之间的数 
		 * @param l 直线
		 * @return 夹角弧度
		 * 
		 */
		public function lineAngle (l:ILine):Number
		{
			//return radian-l.radian;
			var n:Number=Math.abs(Math.atan(l.k - k / 1 + k * l.k));
			return n;
		}
		/**
		 * 将线段向X,Y轴分别移动x,y个距离后形成新的线段 
		 * @param x X轴上移动的距离
		 * @param y Y轴上移动的距离
		 * @return 线段
		 * 
		 */
		public function move (x:Number=0,y:Number=0):Linear
		{
			var sp:Point=new Point(startPoint.x + x,startPoint.y + y);
			var ep:Point=new Point(endPoint.x + x,endPoint.y + y);
			return new Linear(sp,ep);
		}
		/**
		 * 将线段依点p旋转 radian 弧度后得到的新线段
		 * @param radian 旋转的弧度
		 * @param p 旋转的参考点
		 * @return 线段
		 * 
		 */
		public function turn (radian:Number,p:Point=null):Linear
		{
			if (p == null)
			{
				p=new Point  ;
			}
			var sp:Point=Formula.turnPoint(startPoint,radian,p);
			var ep:Point=Formula.turnPoint(endPoint,radian,p);

			return new Linear(sp,ep);
		}
		/**
		 * 返回包含 k 和 b 值的字符串，其格式为 (k=n,b=m)
		 * @return 线段的表现形式
		 * 
		 */
		public function toString ():String
		{
			return "( startPoint:" + _sp + " endPoint:" + _ep + " k=" + k + " b=" + b + ")";
		}
		/**
		 * 将点 p 依 cp 旋转 radian 弧度所成的点
		 * @param p 待旋转的点
		 * @param radian 旋转弧度
		 * @param cp 旋转的参考点
		 * @return 旋转后的点
		 * 
		 */
		private function turnPoint (p:Point,radian:Number,cp:Point=null):Point
		{
			//(x,y)绕任一点（x0，y0）的旋转θ角变换公式为
			//x’=xcos(θ )- ysin(θ) - x0cos(θ)+y0sin(θ)+x0

			//y’=xsin(θ)+ycos(θ) - x0sin(θ) - y0cos(θ)+y0

			var sa:Number=Math.sin(radian);
			var ca:Number=Math.cos(radian);

			var dx:Number=p.x * ca - p.y * sa - cp.x * ca + cp.y * sa + cp.x;
			var dy:Number=p.x * sa + p.y * ca - cp.x * sa - cp.y * ca + cp.y;

			return new Point(dx,dy);
		}
	}
}